<?php

/**
 * BigMath: A math library wrapper that abstracts out the underlying
 * long integer library.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @access private
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

// Do not allow direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

/**
 * Needed for random number generation
 */
require_once 'Auth/OpenID/CryptUtil.php';

/**
 * Need Auth_OpenID::bytes().
 */
require_once 'Auth/OpenID.php';

/**
 * The superclass of all big-integer math implementations
 * @access private
 * @package OpenID
 */
class Auth_OpenID_MathLibrary {
	/**
	 * Given a long integer, returns the number converted to a binary
	 * string.  This function accepts long integer values of arbitrary
	 * magnitude and uses the local large-number math library when
	 * available.
	 *
	 * @param integer $long The long number (can be a normal PHP
	 * integer or a number created by one of the available long number
	 * libraries)
	 * @return string $binary The binary version of $long
	 */
	function longToBinary($long)
	{
		$cmp = $this->cmp($long, 0);
		if ($cmp < 0) {
			$msg = __FUNCTION__ . " takes only positive integers.";
			trigger_error($msg, E_USER_ERROR);
			return null;
		}

		if ($cmp == 0) {
			return "\x00";
		}

		$bytes = array();

		while ($this->cmp($long, 0) > 0) {
			array_unshift($bytes, $this->mod($long, 256));
			$long = $this->div($long, pow(2, 8));
		}

		if ($bytes && ($bytes[0] > 127)) {
			array_unshift($bytes, 0);
		}

		$string = '';
		foreach ($bytes as $byte) {
			$string .= pack('C', $byte);
		}

		return $string;
	}

	/**
	 * Given a binary string, returns the binary string converted to a
	 * long number.
	 *
	 * @param string $binary The binary version of a long number,
	 * probably as a result of calling longToBinary
	 * @return integer $long The long number equivalent of the binary
	 * string $str
	 */
	function binaryToLong($str)
	{
		if ($str === null) {
			return null;
		}

		// Use array_merge to return a zero-indexed array instead of a
		// one-indexed array.
		$bytes = array_merge(unpack('C*', $str));

		$n = $this->init(0);

		if ($bytes && ($bytes[0] > 127)) {
			trigger_error("bytesToNum works only for positive integers.",
						  E_USER_WARNING);
			return null;
		}

		foreach ($bytes as $byte) {
			$n = $this->mul($n, pow(2, 8));
			$n = $this->add($n, $byte);
		}

		return $n;
	}

	function base64ToLong($str)
	{
		$b64 = base64_decode($str);

		if ($b64 === false) {
			return false;
		}

		return $this->binaryToLong($b64);
	}

	function longToBase64($str)
	{
		return base64_encode($this->longToBinary($str));
	}

	/**
	 * Returns a random number in the specified range.  This function
	 * accepts $start, $stop, and $step values of arbitrary magnitude
	 * and will utilize the local large-number math library when
	 * available.
	 *
	 * @param integer $start The start of the range, or the minimum
	 * random number to return
	 * @param integer $stop The end of the range, or the maximum
	 * random number to return
	 * @param integer $step The step size, such that $result - ($step
	 * * N) = $start for some N
	 * @return integer $result The resulting randomly-generated number
	 */
	function rand($stop)
	{
		static $duplicate_cache = array();

		// Used as the key for the duplicate cache
		$rbytes = $this->longToBinary($stop);

		if (array_key_exists($rbytes, $duplicate_cache)) {
			list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
		} else {
			if ($rbytes[0] == "\x00") {
				$nbytes = Auth_OpenID::bytes($rbytes) - 1;
			} else {
				$nbytes = Auth_OpenID::bytes($rbytes);
			}

			$mxrand = $this->pow(256, $nbytes);

			// If we get a number less than this, then it is in the
			// duplicated range.
			$duplicate = $this->mod($mxrand, $stop);

			if (count($duplicate_cache) > 10) {
				$duplicate_cache = array();
			}

			$duplicate_cache[$rbytes] = array($duplicate, $nbytes);
		}

		do {
			$bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
			$n = $this->binaryToLong($bytes);
			// Keep looping if this value is in the low duplicated range
		} while ($this->cmp($n, $duplicate) < 0);

		return $this->mod($n, $stop);
	}
}

/**
 * Exposes BCmath math library functionality.
 *
 * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
 * by the BCMath extension.
 *
 * @access private
 * @package OpenID
 */
class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
	var $type = 'bcmath';

	function add($x, $y)
	{
		return bcadd($x, $y);
	}

	function sub($x, $y)
	{
		return bcsub($x, $y);
	}

	function pow($base, $exponent)
	{
		return bcpow($base, $exponent);
	}

	function cmp($x, $y)
	{
		return bccomp($x, $y);
	}

	function init($number, $base = 10)
	{
		return $number;
	}

	function mod($base, $modulus)
	{
		return bcmod($base, $modulus);
	}

	function mul($x, $y)
	{
		return bcmul($x, $y);
	}

	function div($x, $y)
	{
		return bcdiv($x, $y);
	}

	/**
	 * Same as bcpowmod when bcpowmod is missing
	 *
	 * @access private
	 */
	function _powmod($base, $exponent, $modulus)
	{
		$square = $this->mod($base, $modulus);
		$result = 1;
		while($this->cmp($exponent, 0) > 0) {
			if ($this->mod($exponent, 2)) {
				$result = $this->mod($this->mul($result, $square), $modulus);
			}
			$square = $this->mod($this->mul($square, $square), $modulus);
			$exponent = $this->div($exponent, 2);
		}
		return $result;
	}

	function powmod($base, $exponent, $modulus)
	{
		if (function_exists('bcpowmod')) {
			return bcpowmod($base, $exponent, $modulus);
		} else {
			return $this->_powmod($base, $exponent, $modulus);
		}
	}

	function toString($num)
	{
		return $num;
	}
}

/**
 * Exposes GMP math library functionality.
 *
 * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
 * by the GMP extension.
 *
 * @access private
 * @package OpenID
 */
class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
	var $type = 'gmp';

	function add($x, $y)
	{
		return gmp_add($x, $y);
	}

	function sub($x, $y)
	{
		return gmp_sub($x, $y);
	}

	function pow($base, $exponent)
	{
		return gmp_pow($base, $exponent);
	}

	function cmp($x, $y)
	{
		return gmp_cmp($x, $y);
	}

	function init($number, $base = 10)
	{
		return gmp_init($number, $base);
	}

	function mod($base, $modulus)
	{
		return gmp_mod($base, $modulus);
	}

	function mul($x, $y)
	{
		return gmp_mul($x, $y);
	}

	function div($x, $y)
	{
		return gmp_div_q($x, $y);
	}

	function powmod($base, $exponent, $modulus)
	{
		return gmp_powm($base, $exponent, $modulus);
	}

	function toString($num)
	{
		return gmp_strval($num);
	}
}

/**
 * Define the supported extensions.  An extension array has keys
 * 'modules', 'extension', and 'class'.  'modules' is an array of PHP
 * module names which the loading code will attempt to load.  These
 * values will be suffixed with a library file extension (e.g. ".so").
 * 'extension' is the name of a PHP extension which will be tested
 * before 'modules' are loaded.  'class' is the string name of a
 * {@link Auth_OpenID_MathWrapper} subclass which should be
 * instantiated if a given extension is present.
 *
 * You can define new math library implementations and add them to
 * this array.
 */
function Auth_OpenID_math_extensions()
{
	$result = array();

	if (!defined('Auth_OpenID_BUGGY_GMP')) {
		$result[] =
			array('modules' => array('gmp', 'php_gmp'),
				  'extension' => 'gmp',
				  'class' => 'Auth_OpenID_GmpMathWrapper');
	}

	$result[] = array(
					  'modules' => array('bcmath', 'php_bcmath'),
					  'extension' => 'bcmath',
					  'class' => 'Auth_OpenID_BcMathWrapper');

	return $result;
}

/**
 * Detect which (if any) math library is available
 */
function Auth_OpenID_detectMathLibrary($exts)
{
	$loaded = false;

	foreach ($exts as $extension) {
		// See if the extension specified is already loaded.
		if ($extension['extension'] &&
			extension_loaded($extension['extension'])) {
			$loaded = true;
		}

		// Try to load dynamic modules.
		if (!$loaded) {
			foreach ($extension['modules'] as $module) {
				if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
					$loaded = true;
					break;
				}
			}
		}

		// If the load succeeded, supply an instance of
		// Auth_OpenID_MathWrapper which wraps the specified
		// module's functionality.
		if ($loaded) {
			return $extension;
		}
	}

	return false;
}

/**
 * {@link Auth_OpenID_getMathLib} checks for the presence of long
 * number extension modules and returns an instance of
 * {@link Auth_OpenID_MathWrapper} which exposes the module's
 * functionality.
 *
 * Checks for the existence of an extension module described by the
 * result of {@link Auth_OpenID_math_extensions()} and returns an
 * instance of a wrapper for that extension module.  If no extension
 * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
 * returned, which wraps the native PHP integer implementation.  The
 * proper calling convention for this method is $lib =&
 * Auth_OpenID_getMathLib().
 *
 * This function checks for the existence of specific long number
 * implementations in the following order: GMP followed by BCmath.
 *
 * @return Auth_OpenID_MathWrapper $instance An instance of
 * {@link Auth_OpenID_MathWrapper} or one of its subclasses
 *
 * @package OpenID
 */
function &Auth_OpenID_getMathLib()
{
	// The instance of Auth_OpenID_MathWrapper that we choose to
	// supply will be stored here, so that subseqent calls to this
	// method will return a reference to the same object.
	static $lib = null;

	if (isset($lib)) {
		return $lib;
	}

	if (Auth_OpenID_noMathSupport()) {
		$null = null;
		return $null;
	}

	// If this method has not been called before, look at
	// Auth_OpenID_math_extensions and try to find an extension that
	// works.
	$ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
	if ($ext === false) {
		$tried = array();
		foreach (Auth_OpenID_math_extensions() as $extinfo) {
			$tried[] = $extinfo['extension'];
		}
		$triedstr = implode(", ", $tried);

		Auth_OpenID_setNoMathSupport();

		$result = null;
		return $result;
	}

	// Instantiate a new wrapper
	$class = $ext['class'];
	$lib = new $class();

	return $lib;
}

function Auth_OpenID_setNoMathSupport()
{
	if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
		define('Auth_OpenID_NO_MATH_SUPPORT', true);
	}
}

function Auth_OpenID_noMathSupport()
{
	return defined('Auth_OpenID_NO_MATH_SUPPORT');
}

?>
